package de.fuberlin.offloading;

import android.content.Context;

public class Algorithms {
//Do not remove the "doSomeLoops" and "fileAndLoops" related code, it is necessary for our offloading engine to work properly
	
	public static final int MAX_REPETITIONS = 20;
	
	private static Engine offloadingEngine = null;
	private static DataBaseHelper dbHelper = null;
	private static long currentAlgInputRep = -1;
	
	//Add the name of your algorithms to this enumeration
	public static enum AlgName {
		doSomeLoops,
		fileAndLoops,
		yourCode,
		doSomeLoops2,
		doublingOf,
		fibonacciRecursive,
		fibonacciIterative,
		randomArraySelectionSort,
		isPrime
	}
	
	public static void setOffloadingEngine(Engine engine) {
		Algorithms.offloadingEngine = engine;
	}
	
	public static String executeLocally(AlgName algName, String... parameters) {
		switch (algName) {
		
		case doSomeLoops:
			long nLoops = Long.parseLong(parameters[0]); //Parsing of the input parameters
			return doSomeLoops(nLoops); //No casting needed of the output result, it is already a String
			
		case fileAndLoops:
			long nLoops2 = Long.parseLong(parameters[0]); //Parsing of the input parameters
			//Parameters[1] is a file encoded as a String with Base64
			//We only want to test that it was correctly received by returning its size, so no parsing is needed
			int fileLength = fileAndLoops(nLoops2, parameters[1]);
			return Integer.toString(fileLength); //In this case, the output parameter is an Integer so casting to String is needed
		
		case yourCode:
			//The input parameters come as Strings in the parameters array
			//Call your algorithm with the required parameters and return the result as a String
			return "TO DO";
			//See the examples below
			
		case doSomeLoops2:
			return vegetables.Zucchini.doSomeLoops2(parameters[0]);
			
		case doublingOf:
			return vegetables.Zucchini.doublingOf(parameters[0]);
			
		case fibonacciIterative:
			return de.fuberlin.enginetesting.SimpleAlgs.fibonacciIterative(parameters[0]);
			
		case fibonacciRecursive:
			return de.fuberlin.enginetesting.SimpleAlgs.fibonacciRecursive(parameters[0]);
			
		case randomArraySelectionSort:
			return de.fuberlin.enginetesting.SimpleAlgs.randomArraySelectionSort(parameters[0]);
			
		case isPrime:
			return de.fuberlin.enginetesting.SimpleAlgs.isPrime(parameters[0]);
			
		default:
			return "Error";
		}
	}

	//Returns the predicted number of low level instructions of the algName algorithm for a given input
	public static double getCost(AlgName algName, String... parameters) {
		switch (algName) {
		
		case doSomeLoops:
			long nLoops = Long.parseLong(parameters[0]); //Parsing of the input parameters
			return doSomeLoopsCost(nLoops);
			
		case fileAndLoops:
			long nLoops2 = Long.parseLong(parameters[0]); //Parsing of the input parameters
			//We have a File encoded as a String in parameters[1], but as fileAndLoops actually behaves like doSomeLoops, we don't need this parameter in order to estimate the cost
			return fileAndLoopsCost(nLoops2);
			
		case yourCode:
			/*Parse the input parameters here, they will come as Strings in the parameters array, cast them statically to what you need
			Then, you have 2 possibilities:
				a)You can define a cost function for your algorithm (it must return a long), then you can call it with the given input parameters
					return yourCodeCostFunction(...);
				b)You can use the automated costs estimation system. You must generate an initial DB through our server and place it in the assets folder of your Android application.
					Then here you must assign the global variable "currentAlgInputRep" a long value representing the current input parameters of your algorithm
					currentAlgInputRep = ...;
			See the examples below*/
			
		case doSomeLoops2:
			long nLoops3 = Long.parseLong(parameters[0]); //Parsing of the input parameters
			currentAlgInputRep = nLoops3; //Transform the input parameters into a representative single value (of the basic Java type long)
			break;
			
		case doublingOf:
			//We don't care about parsing the input parameters, as we always represent the input with the same number (1234)
			//because the cost of this dummy algorithm is actually constant (it is always very small)
			currentAlgInputRep = 1234;
			break;
			
		case fibonacciIterative:
			int fiboSeqItElem = Integer.parseInt(parameters[0]); //Parsing of the input parameters
			return fibonacciIterativeCost(fiboSeqItElem);
			
		case fibonacciRecursive:
			int fiboSeqRecElem = Integer.parseInt(parameters[0]); //Parsing of the input parameters
			return fibonacciRecursiveCost(fiboSeqRecElem);
			
		case randomArraySelectionSort:
			int numOfElements = Integer.parseInt(parameters[0]); //Parsing of the input parameters
			return randomArraySelectionSortCost(numOfElements);
			
		case isPrime:
			int number = Integer.parseInt(parameters[0]); //Parsing of the input parameters
			return isPrimeCost(number);
			
		default:
			return -1.0;
		}
		return estCostWithDB(algName);
	}
	
	//No problem if actually there is no costs DB
	public static void loadAlgCostsDB(Context appContext) {
		dbHelper = new DataBaseHelper(appContext);
		if (dbHelper.isDbInAssets()) {
			//If it has not been done before, copy the DB from the "assets" folder to the "data" folder
			dbHelper.createDataBase();
			//Open the database (we'll keep it open in OPEN_READWRITE mode until onDestroy of the main Activity)
			dbHelper.openDataBase();
		}
	}
	
	//No problem if actually there is no costs DB
	public static void closeAlgCostsDB() {
		dbHelper.close();
	}
	
	public static boolean isAlgInCostsDB(AlgName algName) {
		if (!dbHelper.isDbInAssets()) return false;
		else {
			if (dbHelper.existsAlg(algName.toString())) return true;
			else return false;
		}
	}
	
	private static double estCostWithDB(AlgName algName) {
		double estRunTimeMs = dbHelper.getRuntime(algName.toString(), currentAlgInputRep, offloadingEngine.getCsrFromAlg(algName));
		return estRunTimeMs*Engine.SERVER_INST_MS;
	}
	
	public static void updateCostsDB(AlgName algName, double runtime, boolean serverGen) {
		dbHelper.insertRow(algName.toString(), currentAlgInputRep, runtime, serverGen);
		float recentCsr = dbHelper.getCsr(algName.toString(), currentAlgInputRep, offloadingEngine.getCsrFromAlg(algName));
		if (recentCsr != -1.0) offloadingEngine.updateCsr(algName, recentCsr);
	}

	private static String doSomeLoops(long nLoops) {
		long i = 0;
		while (i < nLoops) i++;
		return "Done";
	}

	private static double doSomeLoopsCost(long nLoops) {
		return nLoops * 5.0;
	}
	
	private static int fileAndLoops(long nLoops, String fileContents) {
		long i = 0;
		while (i < nLoops) i++;
		return fileContents.length();
	}
	
	private static double fileAndLoopsCost(long nLoops) {
		return nLoops * 5.0;
	}
	
	/*
	 * 
	 * 
	 * 
	 * If you are not using the automated costs estimation system,
	 * you can implement your algorithm here and its cost function (which must return a long).
	 * 
	 * On the other hand, if you are using the automated costs estimation system,
	 * your algorithm must be placed in an independent JAR library and no cost function is needed.
	 * 
	 * See the examples below.
	 * 
	 * 
	 * 
	 */
	
	private static double fibonacciIterativeCost(int n) {
		return n * 6.0;
	}
	
	private static double fibonacciRecursiveCost(int n) {
		return Math.pow(2.0, n) * 13.0;
	}
	
	private static double randomArraySelectionSortCost(int n) {
		return n * 255.0 + ((n * (n + 1.0)) / 2) * 12.0;
	}
	
	private static double isPrimeCost(int n) {
		return n * 25.0;
	}

}
